<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="I write many code to write less code.💻">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/Load/">
<meta property="og:title" content="       load 方法全程跟踪 | Gua  ">
<meta property="og:description" content="I write many code to write less code.💻">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       load 方法全程跟踪 | Gua  ">
<meta name="twitter:description" content="I write many code to write less code.💻">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/Load/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>load 方法全程跟踪</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2017-01-16"> 2017-01-16
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
        <i class="fa fa-folder-open-o"></i> 
-->
      </span>
    </div>
  </header>
  <p>几天前 Github 的 <a href="https://github.com/RetVal">RetVal</a> 大神更新了可 debug 版本的 706 <code>&lt;objc/runtime.h&gt;</code> 源码，于是让源码阅读学习得以继续。本文将介绍个人学习 <code>load</code> 方法的全部流程。</p>
<h2 id="load-">load 方法的调用时机</h2>
<p>从 <em>Effective Objective-C 2.0 - 52 Specific Ways to Improve Your iOS and OS X Programs</em> 一书中讲述到：Objective-C 中绝大多数类都继承自 <code>NSObject</code> 根类，每个类都有两个初始化方法，其中之一就是 <code>load</code> 方法。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">+</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="nb">load</span></code></pre></div>
<p>对于每一个 <em>Class</em> 和 <em>Category</em> 来说，必定会调用此方法，而且仅调用一次。当包含 <em>Class</em> 和 <em>Category</em> 的程序库载入系统时，就会执行此方法，并且此过程通常是在程序启动的时候执行。</p>
<p>不同的是，现在的 iOS 系统中已经加入了<strong>动态加载特性（Dynamic Loading）</strong>，这是从 macOS 应用程序中迁移而来的特性，等应用程序启动好之后再去加载程序库。如果 <em>Class</em> 和其 <em>Category</em> 中都重写了 <code>load</code> 方法，则先调用 <em>Class</em> 中的。</p>
<p>我们通过 <a href="https://github.com/RetVal">RetVal</a> 封装好的 debug 版最新源码进行断点调试，来追踪一下 <code>load</code> 方法的全部处理过程，以便于了解这个函数以及 Objective-C 强大的动态性。</p>
<p>创建一个 <em>Class</em> 文件 <code>DGObject.m</code>，然后在其中增加 <code>load</code> 方法。在运行 proj 后，可以看见 <code>load</code> 方法的调用时机是在入口函数主程序之前。</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/14837770930294.jpg" alt="" /></p>
<p>下面在 <code>load</code> 方法下增加断点，查看其调用栈并跟踪函数执行时候的上层代码：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/14837787124422.jpg" alt="" /></p>
<p>调用栈显示栈情况为如下方法对象：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="mi">0</span>  <span class="o">+[</span><span class="no">XXObject</span> <span class="nb">load</span><span class="o">]</span>
<span class="mi">1</span>  <span class="n">call_class_loads</span><span class="p">()</span>
<span class="mi">2</span>  <span class="n">call_load_methods</span>
<span class="mi">3</span>  <span class="n">load_images</span>
<span class="mi">4</span>  <span class="n">dyld</span><span class="o">::</span><span class="n">notifySingle</span><span class="p">(</span><span class="n">dyld_image_states</span><span class="p">,</span> <span class="no">ImageLoader</span> <span class="n">const</span><span class="o">*</span><span class="p">)</span>
<span class="mi">11</span> <span class="n">_dyld_start</span></code></pre></div>
<p>追其源头，从 <code>_dyld_start</code> 开始探究。<strong>dyld(The Dynamic Link Editor)</strong>是 Apple 的动态链接库，系统内核做好启动程序的初始准备后，将其他事务交给 dyld 负责。对于 dyld 这里不再细究，在以后对于动态库的学习时进行研究。</p>
<p>在研究 <code>load_images</code> 方法之前，先来研究一下什么是 <strong>images</strong>。<strong>images</strong>表示的是二进制文件（可执行文件或者动态链接库.so文件）编译后的符号、代码等。所以 <code>load_images</code> 的工作是<strong>传入处理过后的二进制文件并让 <code>Runtime</code> 进行处理</strong>，并且每一个文件对应一个抽象实例来负责加载，这里的实例是 <code>ImageLoader</code>，我们从调用栈的方法 4 可以清楚的看到参数类型：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">dyld</span><span class="o">::</span><span class="n">notifySingle</span><span class="p">(</span><span class="n">dyld_image_states</span><span class="p">,</span> <span class="no">ImageLoader</span> <span class="n">const</span><span class="o">*</span><span class="p">)</span></code></pre></div>
<p><code>ImageLoader</code> 处理二进制文件的时机是在 <code>main</code> 入口函数以前，它在加载文件时主要做两个工作：</p>
<ul>
  <li>在程序运行时它先将动态链接的 image 递归加载 （也就是上面测试栈中一串的递归调用的时刻）</li>
  <li>再从可执行文件 image 递归加载所有符号</li>
</ul>
<h2 id="image">简单了解 image</h2>
<p>在 <a href="https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/%E4%BD%A0%E7%9C%9F%E7%9A%84%E4%BA%86%E8%A7%A3%20load%20%E6%96%B9%E6%B3%95%E4%B9%88%EF%BC%9F.md">你真的了解 load 方法么？</a> 这篇文章中，<em>Draveness</em> 提供了一种断点发来打印出所有加载的镜像。</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/14838483024359.jpg" alt="" /></p>
<p>这样可以将当前载入的 image 全部显示，我们展示的是 image 的 <em>path</em> 和 <em>slice</em> 信息。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">.</span><span class="n">.</span><span class="o">.</span>
<span class="p">(</span><span class="n">const</span> <span class="n">char</span> <span class="o">*</span><span class="p">)</span> <span class="vg">$22</span> <span class="o">=</span> <span class="mh">0x00007fff9c1f07a0</span> <span class="s2">&quot;/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices&quot;</span>
<span class="p">(</span><span class="n">const</span> <span class="n">mach_header</span> <span class="o">*</span><span class="p">)</span> <span class="vg">$23</span> <span class="o">=</span> <span class="mh">0x00007fff9c1f0000</span>
<span class="p">(</span><span class="n">const</span> <span class="n">char</span> <span class="o">*</span><span class="p">)</span> <span class="vg">$24</span> <span class="o">=</span> <span class="mh">0x00007fff9c51bb10</span> <span class="s2">&quot;/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList&quot;</span>
<span class="p">(</span><span class="n">const</span> <span class="n">mach_header</span> <span class="o">*</span><span class="p">)</span> <span class="vg">$25</span> <span class="o">=</span> <span class="mh">0x00007fff9c51b000</span>
<span class="p">(</span><span class="n">const</span> <span class="n">char</span> <span class="o">*</span><span class="p">)</span> <span class="vg">$26</span> <span class="o">=</span> <span class="mh">0x00007fff9ca70d90</span> <span class="s2">&quot;/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation&quot;</span>
<span class="p">(</span><span class="n">const</span> <span class="n">mach_header</span> <span class="o">*</span><span class="p">)</span> <span class="vg">$27</span> <span class="o">=</span> <span class="mh">0x00007fff9ca70000</span>
<span class="p">(</span><span class="n">const</span> <span class="n">char</span> <span class="o">*</span><span class="p">)</span> <span class="vg">$28</span> <span class="o">=</span> <span class="mh">0x00007fff5fbff870</span> <span class="s2">&quot;/Users/Desgard_Duan/Library/Developer/Xcode/DerivedData/objc-frsvxngqnjxvxwahvxtwjglbkjlt/Build/Products/Debug/debug-objc&quot;</span>
<span class="p">(</span><span class="n">const</span> <span class="n">mach_header</span> <span class="o">*</span><span class="p">)</span> <span class="vg">$29</span> <span class="o">=</span> <span class="mh">0x0000000100000000</span></code></pre></div>
<p>这里会传入很多的动态链接库 <code>.dylib</code> 以及官方静态框架 <code>.framework</code> 的 image，而 <em>path</em> 就是其对应的二进制文件的地址。在 <code>&lt;mach-o/dyld.h&gt;</code> 动态库头文件中，也为我们提供了查询所有动态库 image 的方法，在这里也简单介绍一下：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#include &lt;mach-o/dyld.h&gt;</span>
<span class="c1">#include &lt;stdio.h&gt;</span>
<span class="n">void</span> <span class="n">listImages</span><span class="p">(){</span>
    <span class="n">uint32_t</span> <span class="n">i</span><span class="p">;</span>
    <span class="n">uint32_t</span> <span class="n">ic</span> <span class="o">=</span> <span class="n">_dyld_image_count</span><span class="p">();</span>
    <span class="nb">printf</span><span class="p">(</span><span class="s2">&quot;Got %d images</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">ic</span><span class="p">);</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">ic</span><span class="p">;</span> <span class="o">++</span> <span class="n">i</span><span class="p">)</span> <span class="p">{</span>
        <span class="nb">printf</span><span class="p">(</span><span class="s2">&quot;%d: %p</span><span class="se">\t</span><span class="s2">%s</span><span class="se">\t</span><span class="s2">(slide: %p)</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">,</span>
               <span class="n">i</span><span class="p">,</span>
               <span class="n">_dyld_get_image_header</span><span class="p">(</span><span class="n">i</span><span class="p">),</span>
               <span class="n">_dyld_get_image_name</span><span class="p">(</span><span class="n">i</span><span class="p">),</span>
               <span class="n">_dyld_get_image_vmaddr_slide</span><span class="p">(</span><span class="n">i</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="n">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">listImages</span><span class="p">();</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>我们可以通过系统库提供的接口方法，来深入学习官方的动态库情况。</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/14838490225420.jpg" alt="" /></p>
<h2 id="loadimages">继续研究 load_images</h2>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="n">load_images</span>
<span class="sr">//</span> <span class="err">执行</span> <span class="n">dyld</span> <span class="err">提供的并且已被</span> <span class="n">map_images</span> <span class="err">处理后的</span> <span class="n">image</span> <span class="err">中的</span> <span class="o">+</span><span class="nb">load</span>
<span class="sr">//</span> <span class="err">锁定状态：</span><span class="n">runtimeLock</span><span class="err">写操作和</span> <span class="n">loadMethodLock</span> <span class="err">方法，保证线程安全</span>
<span class="n">extern</span> <span class="n">bool</span> <span class="n">hasLoadMethods</span><span class="p">(</span><span class="n">const</span> <span class="n">headerType</span> <span class="o">*</span><span class="n">mhdr</span><span class="p">);</span>
<span class="n">extern</span> <span class="n">void</span> <span class="n">prepare_load_methods</span><span class="p">(</span><span class="n">const</span> <span class="n">headerType</span> <span class="o">*</span><span class="n">mhdr</span><span class="p">);</span>
<span class="n">void</span>
<span class="n">load_images</span><span class="p">(</span><span class="n">const</span> <span class="n">char</span> <span class="o">*</span><span class="n">path</span> <span class="n">__unused</span><span class="p">,</span> <span class="n">const</span> <span class="n">struct</span> <span class="n">mach_header</span> <span class="o">*</span><span class="n">mh</span><span class="p">)</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">没有查询到传入</span> <span class="no">Class</span> <span class="err">中的</span> <span class="nb">load</span> <span class="err">方法，视为锁定状态</span>
    <span class="sr">//</span> <span class="err">则无需给其加载权限，直接返回</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">hasLoadMethods</span><span class="p">((</span><span class="n">const</span> <span class="n">headerType</span> <span class="o">*</span><span class="p">)</span><span class="n">mh</span><span class="p">))</span> <span class="k">return</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">定义可递归锁对象</span>
    <span class="sr">//</span> <span class="err">由于</span> <span class="n">load_images</span> <span class="err">方法由</span> <span class="n">dyld</span> <span class="err">进行回调，所以数据需上锁才能保证线程安全</span>
    <span class="sr">//</span> <span class="err">为了防止多次加锁造成的死锁情况，使用可递归锁解决</span>
    <span class="n">recursive_mutex_locker_t</span> <span class="n">lock</span><span class="p">(</span><span class="n">loadMethodLock</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">收集所有的</span> <span class="o">+</span><span class="nb">load</span> <span class="err">方法</span>
    <span class="p">{</span>
        <span class="sr">//</span> <span class="err">对</span> <span class="no">Darwin</span> <span class="err">提供的线程写锁的封装类</span>
        <span class="n">rwlock_writer_t</span> <span class="n">lock2</span><span class="p">(</span><span class="n">runtimeLock</span><span class="p">);</span>
        <span class="sr">//</span> <span class="err">提前准备好满足</span> <span class="o">+</span><span class="nb">load</span> <span class="err">方法调用条件的</span> <span class="no">Class</span>
        <span class="n">prepare_load_methods</span><span class="p">((</span><span class="n">const</span> <span class="n">headerType</span> <span class="o">*</span><span class="p">)</span><span class="n">mh</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">调用</span> <span class="o">+</span><span class="nb">load</span> <span class="err">方法</span> <span class="p">(</span><span class="n">without</span> <span class="n">runtimeLock</span> <span class="o">-</span> <span class="n">re</span><span class="o">-</span><span class="n">entrant</span><span class="p">)</span>
    <span class="n">call_load_methods</span><span class="p">();</span>
<span class="p">}</span></code></pre></div>
<p>重新回到 <code>load_images</code> 方法，<code>hasLoadMethods</code> 函数引起注意。其中为了查询 <code>load</code> 函数列表，会分别查询该函数在内存数据段上指定 section 区域是否有所记录。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="err">快速查询是否存在</span> <span class="o">+</span><span class="nb">load</span> <span class="err">函数列表</span>
<span class="n">bool</span> <span class="n">hasLoadMethods</span><span class="p">(</span><span class="n">const</span> <span class="n">headerType</span> <span class="o">*</span><span class="n">mhdr</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">size_t</span> <span class="n">count</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">_getObjc2NonlazyClassList</span><span class="p">(</span><span class="n">mhdr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">count</span><span class="p">)</span>  <span class="o">&amp;&amp;</span>  <span class="n">count</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="kp">true</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">_getObjc2NonlazyCategoryList</span><span class="p">(</span><span class="n">mhdr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">count</span><span class="p">)</span>  <span class="o">&amp;&amp;</span>  <span class="n">count</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="kp">true</span><span class="p">;</span>
    <span class="k">return</span> <span class="kp">false</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>在 <code>objc-file.mm</code> 文件中存有以下定义：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="err">类似于</span> <span class="n">C</span><span class="o">++</span> <span class="err">的模板写法，通过宏来处理泛型操作</span>
<span class="sr">//</span> <span class="err">函数内容是从内存数据段的某个区下查询该位置的情况，并回传指针</span>
<span class="c1">#define GETSECT(name, type, sectname)                                   \</span>
    <span class="n">type</span> <span class="o">*</span><span class="nb">name</span><span class="p">(</span><span class="n">const</span> <span class="n">headerType</span> <span class="o">*</span><span class="n">mhdr</span><span class="p">,</span> <span class="n">size_t</span> <span class="o">*</span><span class="n">outCount</span><span class="p">)</span> <span class="p">{</span>              <span class="p">\</span>
        <span class="k">return</span> <span class="n">getDataSection</span><span class="o">&lt;</span><span class="n">type</span><span class="o">&gt;</span><span class="p">(</span><span class="n">mhdr</span><span class="p">,</span> <span class="n">sectname</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="n">outCount</span><span class="p">);</span>     <span class="p">\</span>
    <span class="p">}</span>                                                                   <span class="p">\</span>
    <span class="n">type</span> <span class="o">*</span><span class="nb">name</span><span class="p">(</span><span class="n">const</span> <span class="n">header_info</span> <span class="o">*</span><span class="n">hi</span><span class="p">,</span> <span class="n">size_t</span> <span class="o">*</span><span class="n">outCount</span><span class="p">)</span> <span class="p">{</span>               <span class="p">\</span>
        <span class="k">return</span> <span class="n">getDataSection</span><span class="o">&lt;</span><span class="n">type</span><span class="o">&gt;</span><span class="p">(</span><span class="n">hi</span><span class="o">-&gt;</span><span class="n">mhdr</span><span class="p">(),</span> <span class="n">sectname</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="n">outCount</span><span class="p">);</span> <span class="p">\</span>
    <span class="p">}</span>
<span class="sr">//</span> <span class="err">根据</span> <span class="n">dyld</span> <span class="err">对</span> <span class="n">images</span> <span class="err">的解析来在特定区域查询内存</span>
<span class="no">GETSECT</span><span class="p">(</span><span class="n">_getObjc2ClassList</span><span class="p">,</span>           <span class="n">classref_t</span><span class="p">,</span>      <span class="s2">&quot;__objc_classlist&quot;</span><span class="p">);</span>
<span class="no">GETSECT</span><span class="p">(</span><span class="n">_getObjc2NonlazyCategoryList</span><span class="p">,</span> <span class="n">category_t</span> <span class="o">*</span><span class="p">,</span>    <span class="s2">&quot;__objc_nlcatlist&quot;</span><span class="p">);</span></code></pre></div>
<p>在 Apple 的官方文档中，我们可以在 <code>__DATA</code> 段中查询到 <code>__objc_classlist</code> 的用途，主要是用在<strong>访问 Objective-C 的类列表</strong>，而 <code>__objc_nlcatlist</code> 用于<strong>访问 Objective-C 的 <code>+load</code> 函数列表，比 <code>__mod_init_func</code> 更早被执行</strong>。这一块对类信息的解析是由 dyld 处理时期完成的，也就是我们上文提到的 <code>map_images</code> 方法的解析工作。而且从侧面可以看出，Objective-C 的强大动态性，与 dyld 前期处理密不可分。</p>
<h2 id="section">可递归锁</h2>
<p>在 <code>load_images</code> 方法所在的 <code>objc-runtime-new.mm</code> 中，全局 <code>loadMethodLock</code> 是一个 <code>recursive_mutex_t</code> 类型的变量。这个是苹果公司通过 C 实现的一个互斥递归锁 Class，来解决多次上锁而不会发生死锁的问题。</p>
<p>其作用与 <code>NSRecursiveLock</code> 相同，但不是由 <code>NSLock</code> 再封装，而是通过 C 为 Runtime 的使用场景而写的一个 Class。更多关于线程锁的知识，强烈推荐 <em>bestswifter</em> 这篇博文 <em><a href="https://bestswifter.com/ios-lock/">深入理解 iOS 开发中的锁</a></em>。</p>
<h2 id="load--class">准备 +load 运行的从属 Class</h2>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">void</span> <span class="n">prepare_load_methods</span><span class="p">(</span><span class="n">const</span> <span class="n">headerType</span> <span class="o">*</span><span class="n">mhdr</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">size_t</span> <span class="n">count</span><span class="p">,</span> <span class="n">i</span><span class="p">;</span>
    <span class="n">runtimeLock</span><span class="o">.</span><span class="n">assertWriting</span><span class="p">();</span>
    <span class="sr">//</span> <span class="err">收集</span> <span class="no">Class</span> <span class="err">中的</span> <span class="o">+</span><span class="nb">load</span> <span class="err">方法</span>
    <span class="sr">//</span> <span class="err">获取所有的类的列表</span>
    <span class="n">classref_t</span> <span class="o">*</span><span class="n">classlist</span> <span class="o">=</span> 
        <span class="n">_getObjc2NonlazyClassList</span><span class="p">(</span><span class="n">mhdr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">count</span><span class="p">);</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">通过</span> <span class="n">remapClass</span> <span class="err">获取类指针</span>
        <span class="sr">//</span> <span class="n">schedul_class_load</span> <span class="err">递归到父类逐层载入</span>
        <span class="n">schedule_class_load</span><span class="p">(</span><span class="n">remapClass</span><span class="p">(</span><span class="n">classlist</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">收集</span> <span class="no">Category</span> <span class="err">中的</span> <span class="o">+</span><span class="nb">load</span> <span class="err">方法</span>
    <span class="n">category_t</span> <span class="o">**</span><span class="n">categorylist</span> <span class="o">=</span> <span class="n">_getObjc2NonlazyCategoryList</span><span class="p">(</span><span class="n">mhdr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">count</span><span class="p">);</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">category_t</span> <span class="o">*</span><span class="n">cat</span> <span class="o">=</span> <span class="n">categorylist</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">通过</span> <span class="n">remapClass</span> <span class="err">获取</span> <span class="no">Category</span> <span class="err">对象存有的</span> <span class="no">Class</span> <span class="err">对象</span>
        <span class="no">Class</span> <span class="n">cls</span> <span class="o">=</span> <span class="n">remapClass</span><span class="p">(</span><span class="n">cat</span><span class="o">-&gt;</span><span class="n">cls</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">cls</span><span class="p">)</span> <span class="n">continue</span><span class="p">;</span> 
        <span class="sr">//</span> <span class="err">对类进行第一次初始化，主要用来分配可读写数据空间并返回真正的类结构</span>
        <span class="n">realizeClass</span><span class="p">(</span><span class="n">cls</span><span class="p">);</span>
        <span class="n">assert</span><span class="p">(</span><span class="n">cls</span><span class="o">-&gt;</span><span class="no">ISA</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">isRealized</span><span class="p">());</span>
        <span class="sr">//</span> <span class="err">将需要执行</span> <span class="nb">load</span> <span class="err">的</span> <span class="no">Category</span> <span class="err">添加到一个全局列表中</span>
        <span class="n">add_category_to_loadable_list</span><span class="p">(</span><span class="n">cat</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p><code>prepare_load_methods</code> 作用是为 load 方法做准备，从代码中可以看出 Class 的 load 方法是优先于 Category。其中在收集 Class 的 load 方法中，因为需要对 Class 关系树的根节点逐层遍历运行，在 <code>schedule_class_load</code> 方法中使用深层递归的方式递归到根节点，优先进行收集。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="err">用来规划执行</span> <span class="no">Class</span> <span class="err">的</span> <span class="nb">load</span> <span class="err">方法，包括父类</span>
<span class="sr">//</span> <span class="err">递归调用</span> <span class="o">+</span><span class="nb">load</span> <span class="err">方法通过</span> <span class="n">cls</span> <span class="err">指针以及</span>
<span class="sr">//</span> <span class="err">要求是</span> <span class="n">cls</span> <span class="err">指针的</span> <span class="no">Class</span> <span class="err">必须已经进行链接操作</span>
<span class="n">static</span> <span class="n">void</span> <span class="n">schedule_class_load</span><span class="p">(</span><span class="no">Class</span> <span class="n">cls</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">cls</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">查看</span> <span class="no">RW_REALIZED</span> <span class="err">是否被标记</span>
    <span class="n">assert</span><span class="p">(</span><span class="n">cls</span><span class="o">-&gt;</span><span class="n">isRealized</span><span class="p">());</span>
    <span class="sr">//</span> <span class="err">查看</span> <span class="no">RW_LOADED</span> <span class="err">是否被标记</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">cls</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="no">RW_LOADED</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">递归到深层（超类）运行</span>
    <span class="n">schedule_class_load</span><span class="p">(</span><span class="n">cls</span><span class="o">-&gt;</span><span class="n">superclass</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">将需要执行</span> <span class="nb">load</span> <span class="err">的</span> <span class="no">Class</span> <span class="err">添加到一个全局列表中</span>
    <span class="n">add_class_to_loadable_list</span><span class="p">(</span><span class="n">cls</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">标记</span> <span class="no">RW_LOADED</span> <span class="err">符号</span>
    <span class="n">cls</span><span class="o">-&gt;</span><span class="n">setInfo</span><span class="p">(</span><span class="no">RW_LOADED</span><span class="p">);</span> 
<span class="p">}</span></code></pre></div>
<p>在 <code>schedule_class_load</code> 中，Class 的读取方式是用 cls 指针方式，其中有很多内存符号位用来记录状态。<code>isRealized()</code> 查看的就是 <code>RW_REALIZED</code> 位，该位记录的是<strong>当前 Class 是否初始化一个类的指标</strong>。而之后查看的 <code>RW_LOADED</code> 是记录当前类的 <code>+load</code> 方法是否被被调用。</p>
<p>在存储静态表的方法中，方法对象会以指针的方式作为参数传递，然后用名为 <code>loadable_classes</code> 的静态类数组对即将运行的 load 方法进行存储，其下标索引 <code>loadable_classes_used</code> 为（从零开始的）全局量，并在每次录入方法后做自加操作实现索引的偏移。</p>
<p>由此可以看到，在 <code>prepare_load_methods</code> 方法中，Runtime 方法进行了 Class 和 Category 的筛选过滤工作，并且将即将执行的 load 方法以指针的形式组织成了一个线性表结构，为之后执行操作中打下基础。</p>
<h2 id="load--1">通过函数指针让 load 方法跑起来</h2>
<p>经过加载镜像、缓存类列表后，开始执行 <code>call_load_methods</code> 方法。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">void</span> <span class="n">call_load_methods</span><span class="p">(</span><span class="n">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">是否已经录入</span>
    <span class="n">static</span> <span class="n">bool</span> <span class="n">loading</span> <span class="o">=</span> <span class="no">NO</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">是否有关联的</span> <span class="no">Category</span>
    <span class="n">bool</span> <span class="n">more_categories</span><span class="p">;</span>
    <span class="n">loadMethodLock</span><span class="o">.</span><span class="n">assertLocked</span><span class="p">();</span>
    <span class="sr">//</span> <span class="err">由于</span> <span class="n">loading</span> <span class="err">是全局静态布尔量，如果已经录入方法则直接退出</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">loading</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
    <span class="n">loading</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">声明一个</span> <span class="n">autoreleasePool</span> <span class="err">对象</span>
    <span class="sr">//</span> <span class="err">使用</span> <span class="n">push</span> <span class="err">操作其目的是为了创建一个新的</span> <span class="n">autoreleasePool</span> <span class="err">对象</span>
    <span class="n">void</span> <span class="o">*</span><span class="n">pool</span> <span class="o">=</span> <span class="n">objc_autoreleasePoolPush</span><span class="p">();</span>
    <span class="k">do</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">重复调用</span> <span class="nb">load</span> <span class="err">方法，直到没有</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">loadable_classes_used</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">call_class_loads</span><span class="p">();</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">调用</span> <span class="no">Category</span> <span class="err">中的</span> <span class="nb">load</span> <span class="err">方法</span>
        <span class="n">more_categories</span> <span class="o">=</span> <span class="n">call_category_loads</span><span class="p">();</span>
        <span class="sr">//</span> <span class="err">继续调用，直到所有</span> <span class="no">Class</span> <span class="err">全部完成</span>
    <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">loadable_classes_used</span> <span class="o">&gt;</span> <span class="mi">0</span>  <span class="o">||</span>  <span class="n">more_categories</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">将创建的</span> <span class="n">autoreleasePool</span> <span class="err">对象释放</span>
    <span class="n">objc_autoreleasePoolPop</span><span class="p">(</span><span class="n">pool</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">更改全局标记，表示已经录入</span>
    <span class="n">loading</span> <span class="o">=</span> <span class="no">NO</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>其实 <code>call_load_methods</code> 由以上代码可知，仅是运行 load 方法的入口。其中最重要的方法 <code>call_class_loads</code> 会从一个待加载的类列表  <code>loadable_classes</code> 中寻找对应的类，并使用 <code>selector(load)</code> 的实现并执行。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">static</span> <span class="n">void</span> <span class="n">call_class_loads</span><span class="p">(</span><span class="n">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">声明下标偏移</span>
    <span class="n">int</span> <span class="n">i</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">分离加载的</span> <span class="no">Class</span> <span class="err">列表</span>
    <span class="n">struct</span> <span class="n">loadable_class</span> <span class="o">*</span><span class="n">classes</span> <span class="o">=</span> <span class="n">loadable_classes</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">调用标记</span>
    <span class="n">int</span> <span class="n">used</span> <span class="o">=</span> <span class="n">loadable_classes_used</span><span class="p">;</span>
    <span class="n">loadable_classes</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">;</span>
    <span class="n">loadable_classes_allocated</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">loadable_classes_used</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">调用列表中的</span> <span class="no">Class</span> <span class="err">类的</span> <span class="nb">load</span> <span class="err">方法</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">used</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">获取</span> <span class="no">Class</span> <span class="err">指针</span>
        <span class="no">Class</span> <span class="n">cls</span> <span class="o">=</span> <span class="n">classes</span><span class="o">[</span><span class="n">i</span><span class="o">].</span><span class="n">cls</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">获取方法对象</span>
        <span class="n">load_method_t</span> <span class="n">load_method</span> <span class="o">=</span> <span class="p">(</span><span class="n">load_method_t</span><span class="p">)</span><span class="n">classes</span><span class="o">[</span><span class="n">i</span><span class="o">].</span><span class="n">method</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">cls</span><span class="p">)</span> <span class="n">continue</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="no">PrintLoading</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">_objc_inform</span><span class="p">(</span><span class="s2">&quot;LOAD: +[%s load]</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">cls</span><span class="o">-&gt;</span><span class="n">nameForLogging</span><span class="p">());</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">方法调用</span>
        <span class="p">(</span><span class="o">*</span><span class="n">load_method</span><span class="p">)(</span><span class="n">cls</span><span class="p">,</span> <span class="no">SEL_load</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">释放</span> <span class="no">Class</span> <span class="err">列表</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">classes</span><span class="p">)</span> <span class="n">free</span><span class="p">(</span><span class="n">classes</span><span class="p">);</span>
<span class="p">}</span></code></pre></div>
<p>读完源码，也许会好奇为什么 <code>(*load_method)(cls, SEL_load);</code> 这一句可以调用 load 方法？</p>
<p>其实这是 C 中的<strong>函数指针</strong>基本概念。在这里我用一个简单的例子做个简要说明（如果没有看懂，需要补补基础了0.0）：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#include &lt;stdio.h&gt;</span>
<span class="c1">#import &lt;Foundation/Foundation.h&gt;</span>
<span class="n">void</span> <span class="n">run</span><span class="p">()</span> <span class="p">{</span>
    <span class="nb">printf</span><span class="p">(</span><span class="s2">&quot;Hello World</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">int</span> <span class="n">main</span><span class="p">(</span><span class="n">int</span> <span class="n">argc</span><span class="p">,</span> <span class="n">const</span> <span class="n">char</span> <span class="o">*</span> <span class="n">argv</span><span class="o">[]</span><span class="p">)</span> <span class="p">{</span>
    <span class="vi">@autoreleasepool</span> <span class="p">{</span>
        <span class="n">void</span> <span class="p">(</span><span class="o">*</span><span class="n">dy_run</span><span class="p">)()</span> <span class="o">=</span> <span class="n">run</span><span class="p">;</span>
        <span class="p">(</span><span class="o">*</span><span class="n">dy_run</span><span class="p">)();</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>其结果会发现执行了 <code>run</code> 方法，并输出了 <code>Hello World</code>。这里，我们通过一个 <code>void (*fptr)()</code> 类型的函数指针，将 <code>run</code> 函数获取出，并运行函数。实际上其中的工作是抓取 <code>run</code> 函数的地址并存储在指针变量中。我们通过指针运行对应的地址部分，其效果为执行了 <code>run</code> 函数。</p>
<p>返回方法中的 <code>load_method_t</code>，我们在全局位置发现了该类型的定义：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">typedef</span> <span class="n">void</span><span class="p">(</span><span class="o">*</span><span class="n">load_method_t</span><span class="p">)(</span><span class="nb">id</span><span class="p">,</span> <span class="no">SEL</span><span class="p">);</span></code></pre></div>
<p><code>id</code> 参数可以传递一个类信息，这里是将 <code>cls</code> Class 的指针和 <code>SEL</code> 选择子作为参数传入。</p>
<p>至此完成了 load 方法的动态调用。</p>
<h2 id="class-">全局 Class 存储线性表数据结构</h2>
<p>总结一下 Class 中 load 方法的全部流程，用流程图将其描述一下：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/14844929413362.jpg" alt="" /></p>
<p>下面来研究一下，存储 Class 的全局表数据结构是怎样的。</p>
<p>找到之前的 <code>add_class_to_loadable_list</code> 开始分析：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">void</span> <span class="n">add_class_to_loadable_list</span><span class="p">(</span><span class="no">Class</span> <span class="n">cls</span><span class="p">)</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">定义方法指针</span>
    <span class="sr">//</span> <span class="err">目的是构造函数指针</span>
    <span class="no">IMP</span> <span class="nb">method</span><span class="p">;</span>
    <span class="n">loadMethodLock</span><span class="o">.</span><span class="n">assertLocked</span><span class="p">();</span>
    <span class="sr">//</span> <span class="err">通过</span> <span class="n">cls</span> <span class="err">中的</span> <span class="n">getLoadMethod</span> <span class="err">方法，直接获得</span> <span class="nb">load</span> <span class="err">方法体存储地址</span>
    <span class="nb">method</span> <span class="o">=</span> <span class="n">cls</span><span class="o">-&gt;</span><span class="n">getLoadMethod</span><span class="p">();</span>
    <span class="sr">//</span> <span class="err">没有</span> <span class="nb">load</span> <span class="err">方法直接返回</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">method</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>     
    <span class="k">if</span> <span class="p">(</span><span class="no">PrintLoading</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">_objc_inform</span><span class="p">(</span><span class="s2">&quot;LOAD: class &#39;%s&#39; scheduled for +load&quot;</span><span class="p">,</span> 
                     <span class="n">cls</span><span class="o">-&gt;</span><span class="n">nameForLogging</span><span class="p">());</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">判断数组是否已满</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">loadable_classes_used</span> <span class="o">==</span> <span class="n">loadable_classes_allocated</span><span class="p">)</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">动态扩容，为线性表释放空间</span>
        <span class="n">loadable_classes_allocated</span> <span class="o">=</span> <span class="n">loadable_classes_allocated</span><span class="o">*</span><span class="mi">2</span> <span class="o">+</span> <span class="mi">16</span><span class="p">;</span>
        <span class="n">loadable_classes</span> <span class="o">=</span> <span class="p">(</span><span class="n">struct</span> <span class="n">loadable_class</span> <span class="o">*</span><span class="p">)</span>
            <span class="n">realloc</span><span class="p">(</span><span class="n">loadable_classes</span><span class="p">,</span>
                              <span class="n">loadable_classes_allocated</span> <span class="o">*</span>
                              <span class="n">sizeof</span><span class="p">(</span><span class="n">struct</span> <span class="n">loadable_class</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">将</span> <span class="no">Class</span> <span class="err">指针和方法指针记录</span>
    <span class="n">loadable_classes</span><span class="o">[</span><span class="n">loadable_classes_used</span><span class="o">].</span><span class="n">cls</span> <span class="o">=</span> <span class="n">cls</span><span class="p">;</span>
    <span class="n">loadable_classes</span><span class="o">[</span><span class="n">loadable_classes_used</span><span class="o">].</span><span class="n">method</span> <span class="o">=</span> <span class="nb">method</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">游标自加偏移</span>
    <span class="n">loadable_classes_used</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>在记录过程中，可以看到其 Class 指针和方法指针的记录手段是通过构造 <code>loadable_classes</code> 这个类型的数组进行静态线性表记录。这个类型的数组其数据结构定义如下：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">typedef</span> <span class="n">struct</span> <span class="n">objc_class</span> <span class="o">*</span><span class="no">Class</span><span class="p">;</span>
<span class="n">struct</span> <span class="n">loadable_class</span> <span class="p">{</span>
    <span class="no">Class</span> <span class="n">cls</span><span class="p">;</span>
    <span class="no">IMP</span> <span class="nb">method</span><span class="p">;</span>
<span class="p">};</span></code></pre></div>
<p>其 <code>objc_class</code> 结构笔者在<em><a href="http://www.desgard.com/isa/">用 isa 承载对象的类信息</a></em>一文中有较为详细的介绍，这是对于 Class 的抽象。从此看出，全局 Class 存储线性表结构，内部记录的信息只有 Class 指针和方法指针，这已经足够了。</p>
<p>load 方法的调用情况至此已经全部清晰。思路梳理如下三大流程：</p>
<ul>
  <li>Load Images: 通过 <code>dyld</code> 载入 image 文件，引入 Class。</li>
  <li>Prepare Load Methods: 准备 load 方法。过滤无效类、无效方法，将 load 方法指针和所属 Class 指针收集至全局 Class 存储线性表 <code>loadable_classes</code> 中，其中会涉及到自动扩展空间和父类优先的递归调用问题。</li>
  <li>Call Load Methods: 根据收集到的函数指针，对 load 方法进行动态调用。进一步过滤无效方法，并记录 log 日志。</li>
</ul>
<h2 id="load--2">Load 方法作用</h2>
<p><code>load</code> 方法是我们在开发中最接近 app 启动的可控方法。即在 app 启动以后，入口函数 <code>main</code> 之前。</p>
<p>由于调用有着 <em>non-lazy</em> 属性，并且在运行期只调用一次，于是我们可以使用 <code>load</code> 独有的特性和调用时机来尝试 Method Swizzling。当然因为 load 调用时机过早，并且当多个 Class 没有关联（继承与派生），我们无法知道 Class 中 load 方法的优先调用关系，所以一般不会在 load 方法中引入其他的类，这是在开发当中需要注意的。</p>
<h2 id="section-1">参考文献</h2>
<p><a href="https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/%E4%BD%A0%E7%9C%9F%E7%9A%84%E4%BA%86%E8%A7%A3%20load%20%E6%96%B9%E6%B3%95%E4%B9%88%EF%BC%9F.md">你真的了解 Objective-C 中的load 方法么？</a></p>
<p><a href="http://stackoverflow.com/questions/13326435/nsobject-load-and-initialize-what-do-they-do">NSObject +load and +initialize - What do they do?</a></p>
<p><a href="https://www.mikeash.com/pyblog/friday-qa-2009-05-22-objective-c-class-loading-and-initialization.html">Objective-C Class Loading and Initialization</a></p>
<p><a href="https://medium.com/@kostiakoval/load-vs-initialize-a1b3dc7ad6eb#.5fhb7mfip">+load VS +initialize</a></p>
<p><a href="http://blog.leichunfeng.com/blog/2015/05/02/objective-c-plus-load-vs-plus-initialize/">Objective-C +load vs +initialize</a></p>
<p><a href="http://stackoverflow.com/questions/15315668/objective-c-what-is-a-lazy-class">Objective-C: What is a lazy class?</a></p>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《load 方法全程跟踪》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/Load/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/Load/&amp;title=《load 方法全程跟踪》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/Load/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/Load/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>